﻿using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Rename;
using Microsoft.CodeAnalysis.Text;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace CodeAudit
{
    [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(CodeAuditCodeFixProvider)), Shared]
    public class CodeAuditCodeFixProvider : CodeFixProvider
    {
        public sealed override ImmutableArray<string> FixableDiagnosticIds
        {
            get { return ImmutableArray.Create(CodeAuditAnalyzer.DiagnosticId); }
        }

        public sealed override FixAllProvider GetFixAllProvider()
        {
            // See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/FixAllProvider.md for more information on Fix All Providers
            return WellKnownFixAllProviders.BatchFixer;
        }

        public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
        {
            var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);

            // TODO: Replace the following code with your own analysis, generating a CodeAction for each fix to suggest
            var diagnostic = context.Diagnostics.First();
            var diagnosticSpan = diagnostic.Location.SourceSpan;

            // Find the type declaration identified by the diagnostic.
            var declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType<LocalDeclarationStatementSyntax>().First();

            // Register a code action that will invoke the fix.
            context.RegisterCodeFix(
                CodeAction.Create(
                    title: CodeFixResources.CodeFixTitle,
                    createChangedDocument: c => MakeConstAsync(context.Document, declaration, c),
                    equivalenceKey: nameof(CodeFixResources.CodeFixTitle)),
                diagnostic);
        }
        private static async Task<Document> MakeConstAsync(Document document, LocalDeclarationStatementSyntax localDeclaration, CancellationToken cancellationToken)
        {
            VariableDeclaratorSyntax variableDeclaratorSyntax = localDeclaration.Declaration.Variables.Single();
            string varName = variableDeclaratorSyntax.Identifier.Text;
            string fristStr = (varName[0] + "").ToLower();
            string lastStr = varName.Substring(1, varName.Length - 1);
            varName = fristStr + lastStr;
            SyntaxToken syntaxToken = SyntaxFactory.Identifier(varName);
            LocalDeclarationStatementSyntax newlocalDeclaration = localDeclaration.ReplaceToken(variableDeclaratorSyntax.Identifier, syntaxToken);
            Dictionary<SyntaxNode, SyntaxNode> keyValuePairs = new Dictionary<SyntaxNode, SyntaxNode>();
            keyValuePairs.Add(localDeclaration, newlocalDeclaration);
            // 获取语法树的根节点
            SyntaxNode oldRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            //在下面将localDeclaration的变量名和所有引用一起改成varName
            // 获取语义模型 
            SemanticModel semanticModel = await document.GetSemanticModelAsync(cancellationToken);
            var variableSymbol = semanticModel.GetDeclaredSymbol(variableDeclaratorSyntax, cancellationToken);
            IEnumerable<ReferencedSymbol> references = await SymbolFinder.FindReferencesAsync(variableSymbol, document.Project.Solution, cancellationToken).ConfigureAwait(false);
            // 更新所有引用的变量名
            foreach (var reference in references.SelectMany(r => r.Locations))
            {
                var oldIden = oldRoot.FindNode(reference.Location.SourceSpan, false, true) as IdentifierNameSyntax;
                SyntaxToken newToken = SyntaxFactory.Identifier(varName);
                IdentifierNameSyntax newIden = SyntaxFactory.IdentifierName(newToken);
                keyValuePairs.Add(oldIden, newIden);
            }
            List<SyntaxNode> list = keyValuePairs.Select(x => x.Key).ToList();
            SyntaxNode newRoot = oldRoot;
            newRoot = oldRoot.ReplaceNodes(list, (o, n) => { return keyValuePairs[o]; });
            return document.WithSyntaxRoot(newRoot);
        }
    }
}
